/**
 * Copyright (c) 2011 - 2012 National Library of Medicine
 *
 * Contact: tomasz.adamusiak@nih.gov;
 * 
 * This file is part of LOwlINC
 * 
 * LOwlINC is free software: you can redistribute it and/or modify it under
 * the terms of the GNU Lesser General Public License as published by the Free
 * Software Foundation; either version 3 of the License, or (at your option) any
 * later version.
 * 
 * LOwlINC is distributed in the hope that it will be useful, but WITHOUT
 * ANY WARRANTY; without even the implied warranty of MERCHANTABILITY or FITNESS
 * FOR A PARTICULAR PURPOSE. See the GNU Lesser General Public License for more
 * details.
 * 
 * You should have received a copy of the GNU Lesser General Public License along
 * with LOwlINC. If not, see <http://www.gnu.org/licenses/>.
 */
package gov.nih.nlm.lowlinc.csv;

import gov.nih.nlm.lowlinc.accessionwrapper.LOINCPartID;
import gov.nih.nlm.lowlinc.accessionwrapper.SNOMEDTermID;
import gov.nih.nlm.lowlinc.accessionwrapper.UMLScui;
import gov.nih.nlm.lowlinc.bean.LOINCPart;
import gov.nih.nlm.lowlinc.bean.MRCONSOBean;
import gov.nih.nlm.lowlinc.core.LowlincConfig;
import gov.nih.nlm.lowlinc.owlapi.LowlincException;

import java.io.BufferedReader;
import java.io.FileNotFoundException;
import java.io.FileReader;
import java.lang.reflect.InvocationTargetException;
import java.lang.reflect.Method;
import java.util.ArrayList;
import java.util.HashMap;
import java.util.HashSet;
import java.util.List;
import java.util.Map;
import java.util.Map.Entry;
import java.util.Scanner;
import java.util.Set;

import org.apache.log4j.Logger;

/**
 * @author Tomasz Adamusiak
 * 
 */
public class MrConsoCSVParser {
	private static final Logger log = Logger.getLogger(MrConsoCSVParser.class);
	private String[] headerCSV;
	private final Map<UMLScui, Set<SNOMEDTermID>> umlsSnomed = new HashMap<>();
	private final Map<UMLScui, Set<LOINCPartID>> umlsLoinc = new HashMap<>();
	private final Map<LOINCPartID, Set<SNOMEDTermID>> loincsnomedmap = new HashMap<>();
	private final Map<SNOMEDTermID, Set<LOINCPartID>> snomedloincmap = new HashMap<>();

	public Map<LOINCPartID, Set<SNOMEDTermID>> getLOINCmaps() {
		return loincsnomedmap;
	}

	// probably unnecessary
	@Deprecated
	public Map<SNOMEDTermID, Set<LOINCPartID>> getSNOMEDmaps() {
		return snomedloincmap;
	}

	public MrConsoCSVParser() throws LowlincException {
		try {
			// parse file
			headerCSV = createHeader();
			MRCONSOBean bean;
			Integer cLines = 0;
			Scanner s;
			s = new Scanner(new BufferedReader(new FileReader(
					LowlincConfig.getString("MRCONSO.path"))));

			while (s.hasNextLine()) {
				String line = s.nextLine();
				String[] nextLine = line.split("\\|", -1);

				cLines++;
				bean = bindStringToBean(nextLine);
				if (bean.isObsolete()) {
					continue;
				}

				if (bean.isLOINCPart()) {
					Set<LOINCPartID> concepts = umlsLoinc.get(bean.getCUI());
					if (concepts == null) {
						concepts = new HashSet<>();
						umlsLoinc.put(bean.getCUI(), concepts);
					}
					concepts.add((LOINCPartID) bean.getID());
				} else if (bean.isSNOMED()) {
					Set<SNOMEDTermID> concepts = umlsSnomed.get(bean.getCUI());
					if (concepts == null) {
						concepts = new HashSet<>();
						umlsSnomed.put(bean.getCUI(), concepts);
					}
					concepts.add((SNOMEDTermID) bean.getID());
				}
			}

			log.info(cLines + " MRCONSO lines read. " + umlsSnomed.size()
					+ " SNOMED linked CUIs and " + umlsLoinc.size()
					+ " LOINC parts linked CUIs loaded.");

			// create mappings
			Set<LOINCPartID> sParts = new HashSet<>();
			Set<SNOMEDTermID> sSnomed = new HashSet<>();
			for (UMLScui cui : umlsLoinc.keySet()) {
				// skip if no mappings to snomed
				if (!umlsSnomed.containsKey(cui)) {
					continue;
				}
				sSnomed.addAll(umlsSnomed.get(cui));
				sParts.addAll(umlsLoinc.get(cui));

				for (LOINCPartID id : umlsLoinc.get(cui)) {
					Set<SNOMEDTermID> concepts = loincsnomedmap.get(id);
					if (concepts == null) {
						concepts = new HashSet<>();
						loincsnomedmap.put(id, concepts);
					}
					concepts.addAll(umlsSnomed.get(cui));
				}

				// the other side is not really used anywhere
				// if anything this is for debug
				for (SNOMEDTermID id : umlsSnomed.get(cui)) {
					Set<LOINCPartID> concepts = snomedloincmap.get(id);
					if (concepts == null) {
						concepts = new HashSet<>();
						snomedloincmap.put(id, concepts);
					}
					concepts.addAll(umlsLoinc.get(cui));
				}
			}

			log.info(loincsnomedmap.size() + " LOINC parts map to "
					+ sSnomed.size() + " SNOMED CT terms");
			log.info(snomedloincmap.size() + " SNOMED terms map to "
					+ sParts.size() + " LOINC part terms");

			List<Set<LOINCPartID>> equivLPs = new ArrayList<>();
			// find equivalent LOINC parts
			// enough to test half the set but can't be bothered to optimise the
			// code
			for (Entry<LOINCPartID, Set<SNOMEDTermID>> e1 : loincsnomedmap
					.entrySet()) {
				for (Entry<LOINCPartID, Set<SNOMEDTermID>> e2 : loincsnomedmap
						.entrySet()) {
					if (e1.getKey().equals(e2.getKey())) {
						continue;
					}
					// intersection of the two snomed mappings set
					Set<SNOMEDTermID> intersection = new HashSet<SNOMEDTermID>(
							e1.getValue());
					intersection.retainAll(e2.getValue());
					// if it contains the same snomed id it will result in
					// inferred equivalence
					if (intersection.size() > 0) {
						boolean found = false;
						// find a set of equivalences containing the outer
						// PartID
						for (Set<LOINCPartID> equivSet : equivLPs) {
							if (equivSet.contains(e1.getKey())) {
								equivSet.add(e2.getKey());
								found = true;
							}
						}
						// seed otherwise
						if (!found) {
							Set<LOINCPartID> equivSet = new HashSet<>();
							equivSet.add(e1.getKey());
							equivSet.add(e2.getKey());
							equivLPs.add(equivSet);
						}
					}
				}
			}

			Map<LOINCPartID, LOINCPart> parts = new LOINCPartsCSVParser()
					.getParts();

			Integer counter = 0;
			for (Set<LOINCPartID> set : equivLPs) {
				for (LOINCPartID part : set) {
					part.setVerboseOutput(parts.get(part).getLabel(), parts
							.get(part).getPART_TYPE());
				}
				counter += set.size();
				log.debug(set);
			}
			log.info(equivLPs.size() + " equivalent LP sets found comprising "
					+ counter + " LPs");

			// REPEAT
			// repeat based on CUIs alone (skip snomed mapping)
			equivLPs = new ArrayList<>();
			// find equivalent LOINC parts
			for (Entry<UMLScui, Set<LOINCPartID>> e : umlsLoinc.entrySet()) {
				if (e.getValue().size() > 1) {
					equivLPs.add(e.getValue());
				}
			}

			counter = 0;
			for (Set<LOINCPartID> set : equivLPs) {
				for (LOINCPartID part : set) {
					part.setVerboseOutput(parts.get(part).getLabel(), parts
							.get(part).getPART_TYPE());
				}
				log.debug(set);
				counter += set.size();
			}
			log.info("DIRECT CUI MAPPINGS: " + equivLPs.size()
					+ " equivalent LP sets found comprising " + counter
					+ " LPs");

		} catch (FileNotFoundException e) {
			throw new LowlincException(e);
		}

	}

	private String[] createHeader() {
		String[] header = { "CUI", "LAT", "TS", "LUI", "STT", "SUI", "ISPREF",
				"AUI", "SAUI", "SCUI", "SDUI", "SAB", "TTY", "CODE", "STR",
				"SRL", "SUPPRESS", "CVF" };
		return header;
	}

	private MRCONSOBean bindStringToBean(String[] line) throws LowlincException {
		try {
			MRCONSOBean bean = new MRCONSOBean();

			for (int i = 0; i < headerCSV.length; i++) {
				Method meth = bean.getClass().getMethod("set" + headerCSV[i],
						String.class);
				meth.invoke(bean, line[i]);
			}
			return bean;

		} catch (NoSuchMethodException | SecurityException
				| IllegalAccessException | IllegalArgumentException
				| InvocationTargetException e) {
			throw new LowlincException(e);
		}
	}
}
